#include "cipher.h"

#include <QtDebug>

Cipher::Cipher(QByteArray alphabet, QByteArray defaultKey, CipherAlgorithm algorithm, QObject *parent) :
    QObject(parent), _algorithm(algorithm), _defaultKey(defaultKey), _alphabet(alphabet)
{
    if (alphabet.isNull())
        setBinaryMode();
}

QByteArray Cipher::decrypt(CipherAlgorithm algorithm, QByteArray alphabet, QByteArray ciphertext, QByteArray key)
{
    Cipher c(alphabet, key, algorithm);
    c.setCipherText(ciphertext);
    c.decrypt();
    return c.plainText();
}

QByteArray Cipher::encrypt(Cipher::CipherAlgorithm algorithm, QByteArray alphabet, QByteArray plaintext, QByteArray key)
{
    Cipher c(alphabet, key, algorithm);
    c.setPlainText(plaintext);
    c.encrypt();
    return c.cipherText();
}

void Cipher::setPlainText(QByteArray plainText)
{
    if (_plainText != plainText)
    {
        _plainText = plainText;
//        _cipherText = QByteArray();
        emit plainTextChanged();
    }
}

void Cipher::setCipherText(QByteArray cipherText)
{
    if (_cipherText != cipherText)
    {
        _cipherText = cipherText;
//        _plainText = QByteArray();
        emit cipherTextChanged();
    }
}

void Cipher::setDefaultKey(QByteArray key)
{
    if (_defaultKey != key)
    {
        _defaultKey = key;
        emit defaultKeyChanged();
    }
}

void Cipher::setAlphabet(QByteArray alphabet)
{
    if (_alphabet != alphabet)
    {
        _alphabet = alphabet;
        emit alphabetChanged();
    }
}

void Cipher::setAsciiMode()
{
    QByteArray alp;
    alp.resize(26);
    for (int i = 0; i < 26; i++)
        alp[i] = 'A' + i;
    setAlphabet(alp);
}
void Cipher::setBinaryMode()
{
    QByteArray alp;
    alp.resize(256);
    for (int i = 0; i < 256; i++)
        alp[i] = i;
    setAlphabet(alp);
}

void Cipher::encrypt(QByteArray key, CipherAlgorithm algorithm)
{
    if (key.isNull())
        key = defaultKey();
    if (plainText().isNull())
    {
        qDebug() << "[Cipher::encrypt] Testo in chiaro non presente";
        return;
    }
    if (algorithm == Undefined)
        algorithm = _algorithm;
    switch (algorithm)
    {
    case Undefined:
        qDebug() << "[Cipher::encrypt] specificare un algoritmo di cifratura";
        return;
    case Caesar:
        //Cesare è un caso particolare del vigenere, con lunghezza della chiave = 1
    case Vigenere:
        _cipherText = vigenere(_plainText, key, false);
        return;
    case Autokey:
        _cipherText = autokey(_plainText, key, false);
        return;
    case XORVigenere:
        _cipherText = vigenere(_plainText, key, false, true);
        return;
    case XORAutokey:
        _cipherText = autokey(_plainText, key, false, true);
        return;
    }
}

void Cipher::decrypt(QByteArray key, CipherAlgorithm algorithm)
{
    if (key.isNull())
        key = defaultKey();
    if (cipherText().isNull())
    {
        qDebug() << "[Cipher::decrypt] Testo cifrato non presente";
        return;
    }
    if (algorithm == Undefined)
        algorithm = _algorithm;
    switch (algorithm)
    {
    case Undefined:
        qDebug() << "[Cipher::decrypt] specificare un algoritmo di decifrazione";
        return;
    case Caesar:
        //Cesare è un caso particolare del vigenere, con lunghezza della chiave = 1
    case Vigenere:
        _plainText = vigenere(_cipherText, key, true);
        return;
    case Autokey:
        _plainText = autokey(_cipherText, key, true);
        return;
    case XORVigenere:
        _plainText = vigenere(_cipherText, key, true, true);
        return;
    case XORAutokey:
        _plainText = autokey(_cipherText, key, true, true);
        return;
    }
}

QByteArray Cipher::vigenere(QByteArray plain, QByteArray key, bool decrypt, bool doXor)
{
    if (key.isEmpty()) return plain;

    int n = _alphabet.length();

    //setup key
    int keyLenght = key.length();
    int* _key = new int[keyLenght];
    if (!setupKey(key, _key, decrypt && !doXor)) return plain;

    //start [en|de]crypting
    int offset = 0;
    QByteArray cipher;
    cipher.resize(plain.length());
    if (doXor) for (int index = 0; index < plain.length(); index++)
    {
        int charIndex = _alphabet.indexOf(plain[index]);
        if (charIndex != -1)
            cipher[index] = _alphabet.at(charIndex ^ _key[(index - offset) % keyLenght]);
        else
        {
            //caratteri non alfabetici nel testo
            cipher[index] = plain[index];
            offset++;
        }
    }
    else for (int index = 0; index < plain.length(); index++)
    {
        int charIndex = _alphabet.indexOf(plain[index]);
        if (charIndex != -1)
            cipher[index] = _alphabet.at((charIndex + _key[(index - offset) % keyLenght]) % n);
        else
        {
            //caratteri non alfabetici nel testo
            cipher[index] = plain[index];
            offset++;
        }
    }
    qDebug() << "Vigenere Offset:" << offset;
    delete[] _key;
    return cipher;
}

QByteArray Cipher::autokey(QByteArray plain, QByteArray key, bool decrypt, bool doXor)
{

    if (key.isEmpty()) return plain;
    int n = _alphabet.length();

    //setup key
    int keyLenght = key.length();
    int* _key = new int[keyLenght];
    if (!setupKey(key, _key, decrypt && !doXor)) return plain;

    for (int i = 0; i < keyLenght; i++) if (_key[i] < 0)
        qDebug() << "qualcosa di strano";

    //start [en|de]crypting
    int offset = 0;
    QByteArray cipher;
    cipher.resize(plain.length());
    if (doXor) for (int index = 0; index < plain.length(); index++)
    {
        int charIndex = _alphabet.indexOf(plain[index]);
        if (charIndex != -1)
        {
            cipher[index] = _alphabet.at(charIndex ^ _key[(index - offset) % keyLenght]);
            _key[(index - offset) % keyLenght] = decrypt ? cipher[index] : charIndex;
        }
        else
        {
            //caratteri non alfabetici nel testo
            cipher[index] = plain[index];
            offset++;
        }
    }
    else for (int index = 0; index < plain.length(); index++)
    {
        int charIndex = _alphabet.indexOf(plain[index]);
        if (charIndex != -1)
        {
            cipher[index] = _alphabet.at((charIndex + _key[(index - offset) % keyLenght]) % n);
            _key[(index - offset) % keyLenght] = decrypt ? (n - cipher[index]) % n : charIndex;
        }
        else
        {
            //caratteri non alfabetici nel testo
            cipher[index] = plain[index];
            offset++;
        }
    }
    qDebug() << "Autokey Offset" << offset;
    delete[] _key;
    return cipher;
}

bool Cipher::setupKey(QByteArray in, int *out, bool decrypt)
{
    int n = _alphabet.length();
    int keyLenght = in.length();

    if (decrypt) for (int i = 0; i < keyLenght; i++)
    {
        int j = _alphabet.indexOf(in[i]);
        if (j == -1)
        {
            qDebug() << "[Cipher::setupKey] nella chiave sono presenti caratteri non validi";
            return false;
        }
        out[i] = (n - j) % n;
    }
    else for (int i = 0; i < keyLenght; i++)
    {
        int j = _alphabet.indexOf(in[i]);
        if (j == -1)
        {
            qDebug() << "[Cipher::setupKey] nella chiave sono presenti caratteri non validi";
            return false;
        }
        out[i] = j;
    }
    return true;
}
